#include <crt_util.h>

int _bind_unixsock(_sock_t s, const char* name, size_t len, int af, int scount)
{
    struct sockaddr_un addr;

    s_memset(&addr, 0, sizeof(struct sockaddr_un));

    _file_unlink(name);

    addr.sun_family = af;
    s_strncpy(addr.sun_path, name, len);

    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        goto sockerr;
    }

    if (listen(s, scount) < 0) {
        goto sockerr;
    }
    return S_SUCCESS;
sockerr:
    return S_ERROR;
}

int _bind_stcpsockv4(_sock_t s, const char* name, size_t len, int scount, unsigned short port)
{
    struct sockaddr_in saddr;

    saddr.sin_family = AF_INET;

    if (len == 0)
        saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    else
        _inet_pton(AF_INET, name, (char*)&saddr.sin_addr);

    // saddr.sin_addr.s_addr = inet_addr(name);

    saddr.sin_port = htons(port);

    if (bind(s, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
        goto sockerr;
    }

    if (listen(s, scount) < 0) {
        goto sockerr;
    }

    return S_SUCCESS;
sockerr:
    return S_ERROR;
}
int _bind_stcpsockv6(_sock_t s, const char* name, size_t len, int scount, unsigned short port)
{
    struct sockaddr_in6 saddr6;
    s_memset(&saddr6, 0, sizeof(struct sockaddr_in6));

    saddr6.sin6_family = AF_INET6;
    saddr6.sin6_port = htons(port);

    _inet_pton(AF_INET6, name, (char*)&saddr6.sin6_addr);

    if (bind(s, (struct sockaddr*)&saddr6, sizeof(saddr6)) < 0) {
        goto sockerr;
    }

    if (listen(s, scount) < 0) {
        goto sockerr;
    }

    return S_SUCCESS;
sockerr:
    return S_ERROR;
}

int _connect_unixsock(_sock_t s, const char* name, size_t len, int af)
{
    struct sockaddr_un addr;
    int rc = 0;

    s_memset(&addr, 0, sizeof(struct sockaddr_un));

    addr.sun_family = af;
    s_strncpy(addr.sun_path, name, len);

    rc = connect(s, (struct sockaddr*)&addr, sizeof(addr));
    rc_error(rc < 0, S_SUCCESS);

    return S_ERROR;
}

int _connect_ctcpsockv4(_sock_t s, const char* name, size_t len, unsigned short port)
{
    int rc = 0;

    struct sockaddr_in saddr;
    s_memset(&saddr, 0, sizeof(struct sockaddr_in));

    saddr.sin_family = PF_INET;
    saddr.sin_addr.s_addr = inet_addr(name);
    saddr.sin_port = htons(port);

    rc = connect(s, (struct sockaddr*)&saddr, sizeof(saddr));
    rc_error(rc < 0, S_SUCCESS);

    return S_ERROR;
}
int _connect_ctcpsockv6(_sock_t s, const char* name, size_t len, unsigned short port)
{
    return S_ERROR;
}

#if (TARGET_OS == OS_WIN)

int _socket_geterror(_sock_t sock)
{
    int optval, optvallen = sizeof(optval);
    int err = WSAGetLastError();
    if (err == WSAEWOULDBLOCK && sock >= 0) {
        if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)&optval, &optvallen))
            return err;
        if (optval)
            return optval;
    }
    return err;
}

#elif (TARGET_OS == OS_POSIX)

#elif (TARGET_OS == OS_MACH)

#endif

int _asynconnect_sockv4(_sock_t s, const char* name, size_t len, unsigned short port)
{
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(name);
    addr.sin_port = htons(port & UINT16_MAX);

    return _asynconnect_sock(s, (const struct sockaddr*)&addr, sizeof(struct sockaddr_in));
}
int _asynconnect_sock(_sock_t s, SOCKADDR_PTR sa, socklen_t socklen)
{
    int rc = 0;

    rc = set_blocking(s, 1);

    rc = connect(s, sa, socklen);

    if (rc < 0) {
        int err = _socket_geterror(s);

        if (_ERR_CONNECT_RETRIABLE(err))
            return S_PENDING; // connecting

        if (_ERR_CONNECT_REFUSED(err))
            return S_ERROR;

        if (_ERR_CONNECT_ERRORRESET(err))
            return S_ERROR;

        if (_ERR_CONNECT_ERROR_ENET(err))
            return S_ERROR;

        if (_ERR_CONNECT_ERROR_EHOST(err))
            return S_ERROR;

    } else {
        // The connect succeeded
        return S_COMPLETED;
    }

    return S_ERROR;
}
int _connected_socket(_sock_t s)
{
    int e;
    int r;
    socklen_t elen = sizeof(e);
    r = getsockopt(s, SOL_SOCKET, SO_ERROR, (void*)&e, &elen);
    if (r < 0)
        return S_ERROR;
    if (e) {
        if (_ERR_CONNECT_RETRIABLE(e))
            return S_PENDING;
        return S_ERROR;
    }
    return S_SUCCESS;
}

int socket_cloexec(_sock_t s)
{
#if (TARGET_OS != OS_WIN)
    int flags;
    if ((flags = fcntl(s, F_GETFD, NULL)) < 0) {
        return S_ERROR;
    }
    if (!(flags & FD_CLOEXEC)) {
        if (fcntl(s, F_SETFD, flags | FD_CLOEXEC) == -1) {
            return S_ERROR;
        }
    }
#endif
    return S_SUCCESS;
}

_sock_t _createsock(int af, int type, int protocol)
{
    _sock_t s;
    s = socket(af, type, protocol);
    return s;
}
int _closesock(_sock_t s)
{
    if (s != INVALID_SOCKET) {
#if (TARGET_OS == OS_WIN)
        return closesocket(s);
#elif (TARGET_OS == OS_POSIX)
        return close(s);
#elif (TARGET_OS == OS_MACH)
        return close(s);
#elif (TARGET_OS == OS_UNIX)
        return close(s);
#endif
    }
    return S_ERROR;
}
int _stable__closesock(_sock_t s)
{
    set_linger(s, 1);
    if (s != INVALID_SOCKET) {
#if (TARGET_OS == OS_WIN)
        return closesocket(s);
#elif (TARGET_OS == OS_POSIX)
        return close(s);
#elif (TARGET_OS == OS_MACH)
        return close(s);
#elif (TARGET_OS == OS_UNIX)
        return close(s);
#endif
    }
    return S_ERROR;
}
int _shutdown_sock(_sock_t s, int howrw)
{
    int rc = 0;
    rc_error(s != INVALID_SOCKET, S_ERROR);

    rc = shutdown(s, howrw);

    return rc;
}
int is_listen_socket(_sock_t s)
{
    int val, ret;
    socklen_t len = sizeof(val);
    ret = getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, (char*)&val, &len);
    if (ret == -1) {
        return S_ERROR;
    } else if (val) {
        return S_SUCCESS;
    } else {
        return S_ERROR;
    }
    return S_ERROR;
}
int getsocktype(_sock_t s)
{
    sock_addr addr;
    struct sockaddr* sa = (struct sockaddr*)&addr;

    if (sa->sa_family == AF_UNIX) {
        return AF_UNIX;
    }

    if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6) {
        return sa->sa_family;
    }

    return AF_UNSPEC;
}

#if (TARGET_OS == OS_WIN)

#elif (TARGET_OS == OS_POSIX)

#ifndef O_NONBLOCK
#define PATTERN FNDELAY
#else
#define PATTERN O_NONBLOCK
#endif

#elif (TARGET_OS == OS_MACH)

#ifndef O_NONBLOCK
#define PATTERN FNDELAY
#else
#define PATTERN O_NONBLOCK
#endif

#elif (TARGET_OS == OS_UNIX)

#ifndef O_NONBLOCK
#define PATTERN FNDELAY
#else
#define PATTERN O_NONBLOCK
#endif

#endif

int set_blocking(_sock_t s, int turn)
{
#if (TARGET_OS == OS_WIN)
    {
        unsigned long nonblocking = turn;
        int rc = SOCKET_ERROR;
        rc = ioctlsocket(s, FIONBIO, &nonblocking);
        if (rc == SOCKET_ERROR) {
            return S_ERROR;
        }
    }
#elif (TARGET_OS == OS_POSIX)
    {
        int nonb = PATTERN;
        int flags;
        if ((flags = fcntl(s, F_GETFL, NULL)) == -1) {
            return S_ERROR;
        }

        if (fcntl(s, F_SETFL, turn ? flags | nonb : flags & ~nonb) == -1) {
            return S_ERROR;
        }
    }
#elif (TARGET_OS == OS_MACH)
    {
        int nonb = PATTERN;
        int flags;
        if ((flags = fcntl(s, F_GETFL, NULL)) == -1) {
            return S_ERROR;
        }
        if (fcntl(s, F_SETFL, turn ? flags | nonb : flags & ~nonb) == -1) {
            return S_ERROR;
        }
    }
#endif
    return S_SUCCESS;
}
int set_sockerror(_sock_t s, int turn)
{
    int one = turn;
    setsockopt(s, SOL_SOCKET, SO_ERROR, (void*)&one, sizeof(int));
    return S_SUCCESS;
}
int set_reuseaddr(_sock_t s, int turn)
{
    int one = turn;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&one, sizeof(one));
    return S_SUCCESS;
}
int set_nodelay(_sock_t s, int turn)
{
    rc_error(s != INVALID_SOCKET, S_ERROR);
    return S_SUCCESS;
}
int set_deferaccpet(_sock_t s, int turn)
{
#if defined(TCP_DEFER_ACCEPT)
    socklen_t one = turn;
    setsockopt(s, SOL_SOCKET, TCP_DEFER_ACCEPT, &one, sizeof(socklen_t));
#endif

    return S_SUCCESS;
}
int set_broadcast(_sock_t s, int turn)
{
    int one = turn;
    setsockopt(s, SOL_SOCKET, SO_BROADCAST, (const char*)&one, sizeof(int));
    return S_SUCCESS;
}
int set_dontroute(_sock_t s, int turn)
{
    int one = turn;
    setsockopt(s, SOL_SOCKET, SO_DONTROUTE, (const char*)&one, sizeof(one));
    return S_SUCCESS;
}
int set_dolinger(_sock_t s, int turn)
{
#if (TARGET_OS == OS_WIN)
    int one = turn;
    setsockopt(s, SOL_SOCKET, SO_DONTLINGER, (const char*)&one, sizeof(int));
#endif
    return S_SUCCESS;
}
int set_reuseable(_sock_t s, int turn)
{
#if defined(SO_REUSEADDR) && !defined(_WIN32)
    int one = 1;
    /* REUSEADDR on Unix means, "don't hang on to this address after the
     * listener is closed."  On Windows, though, it means "don't keep other
     * processes from binding to this address while we're using it. */
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, (socklen_t)sizeof(one));
#else

#endif
    return S_SUCCESS;
}
int set_reuseableport(_sock_t s, int turn)
{
#if (TARGET_OS == OS_POSIX) && defined(SO_REUSEPORT)
    int one = 1;
    /* REUSEPORT on Linux 3.9+ means, "Multiple servers (processes or
     * threads) can bind to the same port if they each set the option. */
    setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (void*)&one, (socklen_t)sizeof(one));
#else

#endif
    return S_SUCCESS;
}
int set_linger(_sock_t s, int sec)
{
    struct linger so_linger;
    so_linger.l_onoff = 1;
    so_linger.l_linger = sec;
    setsockopt(s, SOL_SOCKET, SO_LINGER, (const char*)&so_linger, sizeof(so_linger));
    return S_SUCCESS;
}
int set_keepalive(_sock_t s, int turn)
{
    int one = turn;
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (const char*)&one, sizeof(int));
    return S_SUCCESS;
}
int set_cork(_sock_t s, int turn)
{
    int one = 0;
    one = turn;
#if (TARGET_OS == OS_POSIX)
    setsockopt(s, SOL_TCP, TCP_CORK, (void*)&one, sizeof(int));
#endif
    return S_SUCCESS;
}
int set_nosigpipe(_sock_t s, int turn)
{
    int one = 0;
    one = turn;
    // unix no found MSG_NOSIGNAL, but have SO_NOSIGPIPE
#if (TARGET_OS != OS_WIN)

#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL SO_NOSIGPIPE
#endif
    setsockopt(s, SOL_SOCKET, MSG_NOSIGNAL, (void*)&one, sizeof(int));
#endif

    return S_SUCCESS;
}
int set_sendtime(_sock_t s, int sec)
{
    struct timeval timeout;
    timeout.tv_sec = sec;
    timeout.tv_usec = 0;
    socklen_t len = sizeof(timeout);
    setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, len);
    return S_SUCCESS;
}
int set_recvtime(_sock_t s, int sec)
{
    struct timeval timeout;
    timeout.tv_sec = sec;
    timeout.tv_usec = 0;
    socklen_t len = sizeof(timeout);
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, len);
    return S_SUCCESS;
}
int set_connectime(_sock_t s, int sec)
{
    return S_SUCCESS;
}
int set_sendbuflen(_sock_t s, socklen_t size)
{
    int rc = 0;
    socklen_t len = size;
    rc = setsockopt(s, SOL_SOCKET, SO_SNDBUF, (const char*)&len, sizeof(len));
    return rc == 0 ? S_SUCCESS : S_ERROR;
}
int set_recvbuflen(_sock_t s, socklen_t size)
{
    int rc = 0;
    socklen_t len = size;
    rc = setsockopt(s, SOL_SOCKET, SO_RCVBUF, (const char*)&len, sizeof(len));
    return rc == 0 ? S_SUCCESS : S_ERROR;
}
int set_sendlowat(_sock_t s, socklen_t size)
{
    int rc = 0;
    socklen_t value = size;
    rc = setsockopt(s, SOL_SOCKET, SO_SNDLOWAT, (void*)&value, sizeof(value));
    return rc == 0 ? S_SUCCESS : S_ERROR;
}
int set_recvlowat(_sock_t s, socklen_t size)
{
    int rc = 0;
    socklen_t value = size;
    rc = setsockopt(s, SOL_SOCKET, SO_RCVLOWAT, (void*)&value, sizeof(value));
    return rc == 0 ? S_SUCCESS : S_ERROR;
}

#if (TARGET_OS == OS_WIN)

int socketpair(int d, int type, int protocol, _sock_t sv[2])
{
    typedef int socklen_t;
    union {
        struct sockaddr_in inaddr;
        struct sockaddr addr;
    } a;

    _sock_t listener;
    int e;

    socklen_t addrlen = sizeof(a.inaddr);
    DWORD flags = 0;
    int reuse = 1;

    if (sv == 0) {
        WSASetLastError(WSAEINVAL);
        return SOCKET_ERROR;
    }

    listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listener == INVALID_SOCKET)
        return SOCKET_ERROR;

    s_memset(&a, 0, sizeof(a));
    a.inaddr.sin_family = AF_INET;
    a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    a.inaddr.sin_port = 0;

    sv[0] = sv[1] = INVALID_SOCKET;
    do {
        setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&reuse, (socklen_t)sizeof(reuse));
        if (bind(listener, &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR)
            break;
        if (getsockname(listener, &a.addr, &addrlen) == SOCKET_ERROR)
            break;
        if (listen(listener, 1) == SOCKET_ERROR)
            break;
        sv[0] = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, flags);
        if (sv[0] == INVALID_SOCKET)
            break;
        if (connect(sv[0], &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR)
            break;
        sv[1] = accept(listener, NULL, NULL);
        if (sv[1] == INVALID_SOCKET)
            break;

        closesocket(listener);
        return 0;

    } while (0);

    e = WSAGetLastError();
    closesocket(listener);
    closesocket(sv[0]);
    closesocket(sv[1]);
    WSASetLastError(e);
    return SOCKET_ERROR;
}

#endif
